package net.sudot.chess.business.controller.web;

import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import net.sudot.chess.business.model.Customer;
import net.sudot.chess.business.model.SocialUser;
import net.sudot.chess.business.service.SocialUserService;
import net.sudot.chess.business.service.UserService;
import net.sudot.chess.business.wechat.api.WechatApiService;
import net.sudot.chess.constant.SessionConstants;
import net.sudot.chess.exception.BusinessException;
import net.sudot.commons.lock.RedisLock;
import net.sudot.commons.lock.UnlockException;
import net.sudot.commons.security.LoginUser;
import net.sudot.commons.security.SocialUserAuthenticationToken;
import net.sudot.commons.utils.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.concurrent.locks.Lock;

/**
 * 微信重定向导航
 *
 * @author tangjialin on 2018-03-04
 */
@Controller
@RequestMapping("/wechat/route")
public class WechatRouteController {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private WechatApiService wechatApiService;
    @Resource
    private UserService userService;
    @Resource
    private SocialUserService socialUserService;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 菜单跳转路由中心
     * <pre>
     * 如果用户同意授权，页面将跳转至此。redirect_uri/?code=CODE&state=STATE。
     * 若用户禁止授权，则跳转至此不会带上code参数，仅会带上state参数redirect_uri?state=STATE
     * </pre>
     *
     * @param redirect 重定向地址
     * @param code     作为换取access_token的票据，每次用户授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期。
     * @param state    重定向后会带上state参数，开发者可以填写a-zA-Z0-9的参数值，最多128字节
     * @param customer 当前登录用户
     * @param request
     * @param response
     * @return
     */
    @RequestMapping
    public String route(@RequestParam(name = "redirect", required = false) String redirect,
                        @RequestParam(name = "code", required = false) String code,
                        @RequestParam(name = "state", required = false) String state,
                        @LoginUser Customer customer,
                        HttpServletRequest request, HttpServletResponse response) {
        String returnView = "redirect:" + redirect;
        if (code == null || code.isEmpty()) {
            logger.warn("拒绝授权.redirect:{} request:{} 授权码:{} 自定义参数:{}", redirect, WebUtils.getRequestFullUrl(request), code, state);
            throw new BusinessException("授权失败,请返回重试");
        }
        try {
            WxMpOAuth2AccessToken accessToken = wechatApiService.oauth2getAccessToken(code);
            if (accessToken == null) {
                logger.warn("OpenId换取失败.redirect:{} request:{} 授权码:{} 自定义参数:{}", redirect, WebUtils.getRequestFullUrl(request), code, state);
                throw new BusinessException("授权失败,请返回重试");
            }
            HttpSession session = request.getSession();
            wechatApiService.setUserToken(session, accessToken);
            String appId = wechatApiService.getWxMpConfigStorage().getAppId();
            String openId = accessToken.getOpenId();
            // 加锁,防止重复请求导致注册两次
            Lock lock = new RedisLock(redisTemplate, appId, openId);
            if (!lock.tryLock()) { throw new UnlockException("授权失败,请返回重试"); }
            try {
                SocialUser socialUser = socialUserService.findByUniqueId(appId, openId);
                socialUser = socialUser == null ? new SocialUser() : socialUser;
                if (socialUser.getUserId() == null) {
                    if (!socialUser.isSubscribe()) {
                        // 拉取用户信息
                        WxMpUser wxMpUser = wechatApiService.getUserService().userInfo(openId);
                        // 设置关注状态
                        Boolean subscribe = wxMpUser.getSubscribe();
                        socialUser.setSubscribe(subscribe == null ? false : subscribe);
                    }
                    socialUser.setLoginPluginId(appId).setUniqueId(openId);
                    Long inviter = (Long) session.getAttribute(SessionConstants.INVITE_RECORD_INVITER);
                    socialUser = socialUserService.save(socialUser, inviter);
                    session.removeAttribute(SessionConstants.INVITE_RECORD_INVITER);
                }
                // 客户已登录,且登录客户和实际客户一致则放行
                if (customer != null && customer.getId().equals(socialUser.getUserId())) { return returnView; }
                SocialUserAuthenticationToken socialUserAuthenticationToken = new SocialUserAuthenticationToken(socialUser, false, WebUtils.getRequestFullIp(request));
                userService.login(socialUserAuthenticationToken);
                refreshUserInfo(socialUser, accessToken);
            } finally { // 释放锁
                lock.unlock();
            }
        } catch (Exception e) {
            logger.error("微信授权访问异常.redirect:{} request:{} 授权码:{} 自定义参数:{}", redirect, WebUtils.getRequestFullUrl(request), code, state, e);
            throw new BusinessException("授权失败,请返回重试");
        }
        return returnView;
    }

    /**
     * 刷新用户信息
     *
     * @param socialUser  社会化用户信息
     * @param accessToken 用户授权TOKEN
     */
    public Boolean refreshUserInfo(SocialUser socialUser, WxMpOAuth2AccessToken accessToken) {
        try {
            Customer customer = (Customer) userService.find(socialUser.getUserId());
            if (customer.getHead() == null) {
                // 拉取用户信息
                WxMpUser wxMpUser = wechatApiService.getUserService().userInfo(accessToken.getOpenId());
                socialUser.setSubscribe(wxMpUser.getSubscribe());
                if (!wxMpUser.getSubscribe()) {
                    // 若用户未关注公众号,则使用oauth2getUserInfo接口通过accessToken拉取用户信息
                    wxMpUser = wechatApiService.oauth2getUserInfo(accessToken, null);
                }
                customer.setHead(wxMpUser.getHeadImgUrl());
                customer.setNickname(wxMpUser.getNickname());
                userService.update(customer);
            }
            return socialUser.getSubscribe();
        } catch (Exception e) {
            logger.warn("客户信息拉取失败", e);
        }
        return null;
    }
}
